/*
 * Copyright (c) 2018-2999 九五云信息科技有限公司 All rights reserved.
 *
 * https://www.gz-yami.com/
 *
 */

package com.yami.shop.service.impl;


import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.yami.shop.bean.dto.FlowRouteAnalysisDto;
import com.yami.shop.bean.dto.SankeyDto;
import com.yami.shop.bean.enums.FlowLogPageEnum;
import com.yami.shop.bean.enums.FlowVisitEnum;
import com.yami.shop.bean.model.FlowLog;
import com.yami.shop.bean.model.FlowRouteAnalysis;
import com.yami.shop.bean.param.FlowAnalysisParam;
import com.yami.shop.bean.param.FlowRouteAnalysisParam;
import com.yami.shop.bean.param.RouteAnalysisParam;
import com.yami.shop.common.util.Json;
import com.yami.shop.dao.FlowRouteAnalysisMapper;
import com.yami.shop.service.FlowRouteAnalysisService;
import lombok.AllArgsConstructor;
import ma.glasnost.orika.MapperFacade;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 流量分析—用户路径数据
 *
 * @author YXF
 * @date 2020-07-14 11:26:35
 */
@Service
@AllArgsConstructor
public class FlowRouteAnalysisServiceImpl extends ServiceImpl<FlowRouteAnalysisMapper, FlowRouteAnalysis> implements FlowRouteAnalysisService {

    private final FlowRouteAnalysisMapper flowRouteAnalysisMapper;
    private final MapperFacade mapperFacade;
    /**
     * 数据显示的最大步骤数
     */
    private final Integer MAX_STEP = 10;

    @Override
    public void routeInfo(List<FlowLog> flowLogList, Date startTime) {
        //路径分析只需要访问的页面 -》 对flowLogList进行筛选，点击和加购的数据去除掉
        List<FlowLog> visitOrShareList = flowLogList.stream().filter(flowAnalysisLog -> FlowVisitEnum.isVisitOrShare(flowAnalysisLog.getVisitType())).collect(Collectors.toList());
        //筛选后剩下的页面数据按照步骤数进行分组 -》分组的数据用于路径步骤的统计
        Map<Integer, List<FlowLog>> stepMap = visitOrShareList.stream().collect(Collectors.groupingBy(FlowLog::getStep));
        //Map<step(步骤数), RouteAnalysisParam> 保存统计的路径数据
        Map<Integer, RouteAnalysisParam> routeAnalysisMap = new HashMap<>();
        Set<Integer> stepSet = stepMap.keySet();
        for (Integer key:stepSet){
            if (key > MAX_STEP){
                //只取用户前十步的操作数据
                continue;
            }
            if (!routeAnalysisMap.containsKey(key)){
                RouteAnalysisParam routeAnalysisParam = new RouteAnalysisParam();
                routeAnalysisParam.setStep(key);
                routeAnalysisMap.put(key, routeAnalysisParam);
            }
            //获取该步骤数的用户操作数据
            Map<Integer, RouteAnalysisParam> routeAnalysisAsStep = routeAnalysisMap.get(key).getFlowRouteAnalysisParamMap();
            for (FlowLog flowLog: stepMap.get(key)){
                //系统类型
                Integer systemType = flowLog.getSystemType();
                //路径数据按照系统类型分别进行统计
                if (!routeAnalysisAsStep.containsKey(systemType)){
                    //该系统类型，第一次进来时，没有数据 —》 创建该系统类型的集合
                    RouteAnalysisParam routeAnalysisParam = new RouteAnalysisParam();
                    routeAnalysisParam.setSystemType(systemType);
                    routeAnalysisAsStep.put(systemType, routeAnalysisParam);
                }
                //获取该用户操作对应的系统类型
                Map<Integer, FlowRouteAnalysisDto> flowRouteAnalysisAsSystemType = routeAnalysisAsStep.get(systemType).getFlowRouteAnalysisDtoMap();
                //路径数据的统计，若没有该步骤下对应的页面信息，则把当前用户操作的信息作为页面信息
                if (!flowRouteAnalysisAsSystemType.containsKey(flowLog.getPageId())){
                    FlowRouteAnalysisDto flowRouteAnalysisDto = new FlowRouteAnalysisDto();
                    flowRouteAnalysisDto.setStep(key);
                    flowRouteAnalysisDto.setSystemType(flowLog.getSystemType());
                    flowRouteAnalysisDto.setPageId(flowLog.getPageId());
                    flowRouteAnalysisDto.setCreateDate(startTime);
                    flowRouteAnalysisAsSystemType.put(flowLog.getPageId(), flowRouteAnalysisDto);
                }
                // 统计路径信息
                getFlowRouteAnalysisInfo(flowLog, flowRouteAnalysisAsSystemType);
            }
        }

        //插入数据到数据库
        List<FlowRouteAnalysis> flowRouteAnalysisList = Lists.newArrayList();
        //按照步骤数循环
        for (Integer step:routeAnalysisMap.keySet()){
            Map<Integer, RouteAnalysisParam> flowRouteAnalysisAsStep = routeAnalysisMap.get(step).getFlowRouteAnalysisParamMap();
            //按照系统类型循环
            for (Integer systemType: flowRouteAnalysisAsStep.keySet()){
                Map<Integer, FlowRouteAnalysisDto> flowRouteAnalysisDtoMap = flowRouteAnalysisAsStep.get(systemType).getFlowRouteAnalysisDtoMap();
                routeAnalysisByPageId(flowRouteAnalysisDtoMap, flowRouteAnalysisList);
            }
        }
        saveBatch(flowRouteAnalysisList);
    }

    /**
     * 处理路径的页面数据
     * @param flowRouteAnalysisDtoMap 页面数据集合
     * @param flowRouteAnalysisList 保存处理后的页面数据
     */
    private void routeAnalysisByPageId(Map<Integer, FlowRouteAnalysisDto> flowRouteAnalysisDtoMap, List<FlowRouteAnalysis> flowRouteAnalysisList) {
        //按照页面编号循环
        for (FlowRouteAnalysisDto flowRouteAnalysisDto:flowRouteAnalysisDtoMap.values()){
            FlowRouteAnalysis flowRouteAnalysisNew = mapperFacade.map(flowRouteAnalysisDto, FlowRouteAnalysis.class);
            flowRouteAnalysisNew.setSessionNums(flowRouteAnalysisDto.getSessionSet().size());
            //统计/处理下一步操作的数据
            for (FlowRouteAnalysis.NextPageDateModel nextPageDateModel:flowRouteAnalysisDto.getNextPageDateModelMap().values()){
                nextPageDateModel.setSessionNum(nextPageDateModel.getSessionNumList().size());
                nextPageDateModel.setSessionNumList(null);
            }
            flowRouteAnalysisNew.setNextPageDate(Json.toJsonString(flowRouteAnalysisDto.getNextPageDateModelMap().values()));
            flowRouteAnalysisList.add(flowRouteAnalysisNew);
        }
    }

    @Override
    public Date maxCreateDate() {
        return flowRouteAnalysisMapper.maxCreateDate();
    }

    @Override
    public SankeyDto getRoutData(FlowRouteAnalysisParam flowRouteAnalysisParam) {
        SankeyDto sankeyDto = new SankeyDto();
        List<FlowRouteAnalysis> flowRouteAnalysisList = flowRouteAnalysisMapper.selectList(new LambdaQueryWrapper<FlowRouteAnalysis>()
                .eq(!flowRouteAnalysisParam.getSystemType().equals(0), FlowRouteAnalysis::getSystemType, flowRouteAnalysisParam.getSystemType())
                .ge(FlowRouteAnalysis::getCreateDate, flowRouteAnalysisParam.getStartTime())
                .lt(FlowRouteAnalysis::getCreateDate, flowRouteAnalysisParam.getEndTime())
        );
        //Map<step(步骤数), List<FlowRouteAnalysis>>
        Map<Integer, List<FlowRouteAnalysis>> flowRouteAnalysisAsStep = flowRouteAnalysisList.stream().collect(Collectors.groupingBy(FlowRouteAnalysis::getStep));
        List<String> names = Lists.newArrayList();
        if (!flowRouteAnalysisAsStep.containsKey(1)){
            return new SankeyDto();
        }
        //Map<step(步骤数),Map<pageId(页面编号),FlowRouteAnalysis>>
        Map<Integer,Map<Integer,FlowRouteAnalysis>> routeAsStep = new HashMap<>();
        for (int key=1;key<=10;key++){
            if (!flowRouteAnalysisAsStep.containsKey(key)){
                break;
            }
            List<FlowRouteAnalysis> flowRouteAnalysiss = flowRouteAnalysisAsStep.get(key);
            if (!routeAsStep.containsKey(key)){
                routeAsStep.put(key,new HashMap<>());
            }
            //Map<步骤数, FlowRouteAnalysis>
            Map<Integer, FlowRouteAnalysis> flowRouteAnalysisMap = routeAsStep.get(key);
            //Map<页面编号,FlowRouteAnalysis.NextPageDateModel>
            Map<Integer,FlowRouteAnalysis.NextPageDateModel> nextPageDateModelMap = new HashMap<>();
            FlowRouteAnalysis routeAnalysisData = null;
            //统计每个页面的数据
            for (FlowRouteAnalysis routeAnalysis:flowRouteAnalysiss){
                List<FlowRouteAnalysis.NextPageDateModel> nextPageDateModels = Json.parseArray(routeAnalysis.getNextPageDate(), FlowRouteAnalysis.NextPageDateModel[].class);
                routeAnalysis.setNextPageDateModel(nextPageDateModels);
                //还没有该步骤下对应的页面，则创建页面
                if (!flowRouteAnalysisMap.containsKey(routeAnalysis.getPageId())){
                    routeAnalysisData = routeAnalysis;
                    routeAnalysisData.setNextPageDateModel(Json.parseArray(routeAnalysis.getNextPageDate(),FlowRouteAnalysis.NextPageDateModel[].class));
                    for (FlowRouteAnalysis.NextPageDateModel nextPageDate:nextPageDateModels){
                        nextPageDateModelMap.put(nextPageDate.getPageId(),nextPageDate);
                    }
                    flowRouteAnalysisMap.put(routeAnalysis.getPageId(),routeAnalysisData);
                    continue;
                }
                //获取页面数据
                routeAnalysisData = flowRouteAnalysisMap.get(routeAnalysis.getPageId());
                //统计会话数量
                routeAnalysisData.setSessionNums(routeAnalysisData.getSessionNums() + routeAnalysis.getSessionNums());
                //统计流失数（前端暂无此字段）
//                routeAnalysisData.setLossNums(routeAnalysisData.getLossNums() + routeAnalysis.getLossNums());
                //统计该页面下一步跳转的页面信息
                List<FlowRouteAnalysis.NextPageDateModel> nextPageDateModelList = Json.parseArray(routeAnalysis.getNextPageDate(),FlowRouteAnalysis.NextPageDateModel[].class);
                for (FlowRouteAnalysis.NextPageDateModel nextPageDate:nextPageDateModelList){
                    if (!nextPageDateModelMap.containsKey(nextPageDate.getPageId())){
                        nextPageDateModelMap.put(nextPageDate.getPageId(),nextPageDate);
                        continue;
                    }
                    FlowRouteAnalysis.NextPageDateModel nextPageDateModel = nextPageDateModelMap.get(nextPageDate.getPageId());
                    //下一步跳转的会话数量
                    nextPageDateModel.setSessionNum(nextPageDateModel.getSessionNum() + nextPageDate.getSessionNum());
                }
            }
        }
        int id = 0;
        List<SankeyDto.Node> nodeList = Lists.newArrayList();
        List<SankeyDto.NodeProperty> nodePropertyList = Lists.newArrayList();
        //拼接桑基图的数据（每个步骤的页面信息）
        for (int key=1;key<=10;key++) {
            //处理数据  Map<Integer,Map<Integer,FlowRouteAnalysis>> routeAsStep
            if (!routeAsStep.containsKey(key)){
                break;
            }
            List<FlowRouteAnalysis> flowRouteAnalysisAsKey = routeAsStep.get(key).values().stream().collect(Collectors.toList());
            flowRouteAnalysisAsKey.sort(Comparator.comparingInt(FlowRouteAnalysis::getSessionNums).reversed());
            for (FlowRouteAnalysis flowRouteAnalysis:flowRouteAnalysisAsKey) {
                SankeyDto.Node node = new SankeyDto.Node();
                //页面名称
                node.setName(FlowLogPageEnum.name(flowRouteAnalysis.getPageId()));
                //步骤数
                node.setLevel(key-1);
                nodeList.add(node);
                SankeyDto.NodeProperty nodeProperty = new SankeyDto.NodeProperty();
                //node中对应的索引
                nodeProperty.setId(id);
                //数据大小
                nodeProperty.setValue(flowRouteAnalysis.getSessionNums());
                nodePropertyList.add(nodeProperty);
                flowRouteAnalysis.setId(id);
                id++;
            }
        }
        sankeyDto.setNode(nodeList);
        int edgeId = 0;
        List<SankeyDto.Edge> edgeList = Lists.newArrayList();
        List<SankeyDto.EdgeProperty> edgePropertyList = Lists.newArrayList();
        //拼接桑基图的连接数据
        for (int key=1;key<=10;key++){
            if (!routeAsStep.containsKey(key)){
                break;
            }
            List<FlowRouteAnalysis> flowRouteAnalysisAsKey = routeAsStep.get(key).values().stream().collect(Collectors.toList());
            for (FlowRouteAnalysis flowRouteAnalysis:flowRouteAnalysisAsKey){
                if (routeAsStep.containsKey(key + 1)) {
                    Map<Integer, FlowRouteAnalysis> routeAnalysisAsNextKey = routeAsStep.get(key + 1).values().stream().collect(Collectors.toMap(FlowRouteAnalysis::getPageId,f->f));
                    List<FlowRouteAnalysis.NextPageDateModel> nextPageDateModel = flowRouteAnalysis.getNextPageDateModel();
                    for (FlowRouteAnalysis.NextPageDateModel nextPageDate : nextPageDateModel) {
                        if (!routeAnalysisAsNextKey.containsKey(nextPageDate.getPageId())) {
                            continue;
                        }
                        //连接上下级的索引
                        SankeyDto.Edge edge = new SankeyDto.Edge();
                        //上下级连接的数据大小
                        SankeyDto.EdgeProperty edgeProperty = new SankeyDto.EdgeProperty();
                        FlowRouteAnalysis nextRouteAnalysis = routeAnalysisAsNextKey.get(nextPageDate.getPageId());
                        //上一级的索引
                        edge.setStartNode(flowRouteAnalysis.getId());
                        //下一级的索引
                        edge.setEndNode(nextRouteAnalysis.getId());
                        edgeList.add(edge);
                        //edge 中对应的索引
                        edgeProperty.setId(edgeId);
                        //连接的数据大小
                        edgeProperty.setValue(nextPageDate.getSessionNum());
                        edgePropertyList.add(edgeProperty);
                        edgeId++;
                    }
                }
            }
        }
        SankeyDto.Graph graphs = new SankeyDto.Graph();
        graphs.setNodeProperty(nodePropertyList);
        graphs.setEdgeProperty(edgePropertyList);
        sankeyDto.setEdge(edgeList);
        sankeyDto.setGraph(new SankeyDto.Graph[]{graphs});
        return sankeyDto;
    }

    /**
     * 根据用户操作的页面信息，统计路径数据
     * @param flowLog
     * @param routeAnalysisMap
     */
    private void getFlowRouteAnalysisInfo(FlowLog flowLog, Map<Integer,FlowRouteAnalysisDto> routeAnalysisMap) {
        //获取用户操作对应页面的数据 -》 统计信息
        FlowRouteAnalysisDto flowRouteAnalysisDto = routeAnalysisMap.get(flowLog.getPageId());
        //流失数（用户仅在该也页面停留一秒及一秒以下，则视为流失用户）
        System.out.println(flowLog.toString());
        if (flowLog.getStopTime().equals(1L)){
            flowRouteAnalysisDto.setLossNums(flowRouteAnalysisDto.getLossNums() + 1);
        }
        //flowRouteAnalysisDto.getSessionSet() -》 统计该步骤下页面的会话数量
        flowRouteAnalysisDto.getSessionSet().add(flowLog.getUuidSession());
        //该页面的下一步操作页面的页面编号（在 flowPageAnalysisService.getPageDataByLog() 中已经插入了下一步操作的页面）
        Integer nextPageId = flowLog.getNextPageId();
        Map<Integer, FlowRouteAnalysis.NextPageDateModel> nextPageDateModelMap = flowRouteAnalysisDto.getNextPageDateModelMap();
        FlowRouteAnalysis.NextPageDateModel nextPageDateModel = null;
        //还没有下一步操作的页面数据-》新建一个
        if (!nextPageDateModelMap.containsKey(nextPageId)){
            nextPageDateModel = new FlowRouteAnalysis.NextPageDateModel();
            nextPageDateModel.setPageId(nextPageId);
            nextPageDateModel.setSessionNumList(Lists.newArrayList());
            nextPageDateModelMap.put(nextPageId, nextPageDateModel);
        }
        //已存在下一步操作的页面数据 —》 数据统计
        nextPageDateModel = nextPageDateModelMap.get(nextPageId);
        nextPageDateModel.getSessionNumList().add(flowLog.getUuidSession());
    }
}
